WMS

WMS 基础

Window

Window 是什么?

Window 是一个窗口的概念,是所有 View 的直接管理者,任何视图都通过 Window 呈现 (如 Activity 和 Dialog 都是 PhoneWindow),点击事件由 Window→DecorView→View。

Window 机制

Window、WindowManager 和 WMS 关系

Window、WindowManager 和 WMS 关系图:
srhqu

Activity、Window、DecorView 和 View 之间的关系

每个 Activity 包含了一个 Window 对象,这个对象是由 PhoneWindow 实现的。而 PhoneWindow 将 DecorView 作为了一个应用窗口的根 View,这个 DecorView 又把屏幕划分为了两个区域:一个是 TitleView,也就是 ActionBar 或者 TitleBar,一个是 ContentView,而我们平时在 Xml 文件中写的布局正好是展示在 ContentView 中的。
c5fqp

WMS 职责?

hv9q4

1、窗口管理

WMS 是窗口的管理者,它负责窗口的启动、添加和删除,另外窗口的大小和层级也是由 WMS 进行管理的;窗口管理的核心成员有 DisplayContent、WindowToken 和 WindowState。

2、窗口动画

窗口动画由 WMS 的动画子系统来负责,动画子系统的管理者为 WindowAnimator。

3、输入系统的中转站

InputManagerService 会对触摸事件进行处理,它会寻找一个最合适的窗口来处理触摸反馈信息,WMS 是窗口的管理者,它作为输入系统的中转站再合适不过了

4、Surface 管理

窗口并不具备绘制的功能,因此每个窗口都需要有一块 Surface 来供自己绘制,为每个窗口分配 Surface 是由 WMS 来完成的。而 SurfaceFlinger 会将 WMS 维护的 Surface 按一定次序混合后显示到屏幕上。

WMS 启动流程

SystemServer.startOtherServices() →
WindowManagerService.main →
通过 runWithScissors() 方法在 android.display 线程初始化 WMS

如何理解 system_server、android.display 和 android.ui 三个线程之间的关系?

oh4ki

ViewRootImpl

什么是 ViewRootImpl?

ViewRootImpl 是 View 和 WindowManager 的纽带。

ViewRootImpl 作用

ViewRootImpl 作为连接 WindowManager 和 DecorView 的纽带,同时实现了 ViewParent 接口,ViewRootImp 作为整个控件树的根部,它是 View 树正常运作的动力所在,控件的测量、布局、绘制以及输入事件的分发都由 ViewRootImpl 控制。

ViewRootImpl 何时被创建?

WindowManagerGlobal.addView 的时候

ActivityThread#handleLaunchActivity() →
ActivityThread#performLaunchActivity() →
ActivityThread#handleResumeActivity() →
ActivityThread#performResumeActivity() →
Activity#onResume()/makeVisible() →
WindowManager#addView(View view/DecorView/, ViewGroup.LayoutParams params) →
WindowManagerImpl#addView() →
WindowManagerGlobal#addView()(new ViewRootImpl)→
ViewRootImpl#setView(View view, WindowManager.LayoutParams attrs, View panelParentView) →

WMS 相关问题

从 Activity 创建到 View 呈现中间发生了什么?

Activity、Window、WindowManager、DecorView、ViewRootImpl 作用

从 Activity 创建到 View 呈现中间发生了什么?

idtux

Activity 创建

ActivityThread.handleLaunchActivity
我们通过 startActivity 时,如果目标 Activity 的进程未创建,AMS 会通过 Socket 通知 Zygote 进程 fork 出应用进程;然后一堆调用链,最终会调用到 ActivityThread 的 handleLaunchActivity() 方法

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    // ...
    // performLaunchActivity
    Activity a = performLaunchActivity(r, customIntent);
    if (a != null) {
        r.createdConfig = new Configuration(mConfiguration);
        Bundle oldState = r.state;       
        // handleResumeActivity
        handleResumeActivity(r.token, false, r.isForward,!r.activity.mFinished && !r.startsNotResumed);
        // ...
    }
}

handleLaunchActivity() 主要调用了两个方法:performLaunchActivity() 和 handleResumeActivity()

PhoneWindow.setContentView 创建 DecorView

在 Activity.onCreate 会调用 setContentView 方法,它是调用的 PhoneWindow 的 setContentView 方法,主要是根据不同的 Activity Theme 初始化 DecorView,加载不同的布局。

handleResumeActivity 创建 Activity

WindowManager.addView

ViewRootImpl.scheduleTraversals

为什么要有设计 Window?

  1. 假如没有 Window,那 Window 管理 View 树的代码必然会放到 Activity 中。这样 Activity 就变得十分庞大,这与我们前面说的 Activity 指挥官的角色相违背。
  2. 把 View 树的管理工作封装到 Window 后,在调用 Dialog.show()、Dialog.hide() 等 Window 切换时,Activity 只需要负责 Window 的显示和隐藏即可。
  3. View 的测量、布局、绘制只是在 View 树内进行的,把一个 View 树封装在一个 Window 中方便视图管理。

Window、View 和 ViewRootImpl

每个 Window 对应一个 ViewTree,其根节点是 ViewRootImpl,ViewRootImpl 自上而下地控制着 ViewTree 的一切(绘制、事件和 UI 更新)

子线程真的不能更新 UI 吗?

主线程、UI 线程和子线程概念?

UI 线程: 创建 ViewRootImpl 的线程,最终执行 View 的 measure/layout/draw 等 UI 操作的线程;且需是 Looper 线程

主线程: 创建 ActivityThread 的线程;主线程也是 UI 线程;也是 Looper 线程
**子线程:**非 UI 线程和主线程的子线程,可能是 Looper 线程

子线程能 requestLayout?

对应的线程需要创建 Looper 并且调用 Looper#loop 方法,开启消息循环。

  1. 创建 ViewRootImpl 和执行 UI 更新的线程不在同一个线程,会抛异常

子线程能不能 invalidate?

看情况。子线程能更新 ui 的情况:

  1. ViewRootImpl 未创建,可以更新

在 ViewRootImpl 创建之前 invalidate 不受线程限制,Activity 的 onResume 后,ViewRootImpl 创建了

  1. ViewRootImpl 已创建

为什么 Google 设计成创建 ViewRootImpl 和执行 UI 更新的线程需要在同一个线程?

  1. 如果在不同的线程去操纵一个控件,由于网络延迟或大量耗时操作,会使 UI 绘制混乱,出了问题也很难去排查是哪个线程出了问题
  2. UI 线程非安全线程,如果要保证安全就需要加锁,锁的阻塞会导致其他线程对 View 的访问效率低下

ViewRootImpl 的线程?CalledFromWrongThreadException 哪里来?checkThread 时机?ViewRootImpl 创建时机?

class ViewRootImpl {
    final Thread mThread;
    public ViewRootImpl() {
        mThread = Thread.currentThread();
    }
    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }
}

checkThread() 的时机?

ViewRootImpl 何时被创建?
在 WindowManagerGlobal.addView 的时候

View 已经被 attach 到 Window 后,为什么非 UI 线程不能更新 UI?

当更新 UI 时,ViewRootImpl 会调用 checkThread 方法去检查当前访问 UI 的线程是否为创建 UI 的那个线程,如果不是。则会抛出异常。

在 Activity 的 onCreate/onStart/onResume 是可以在子线程更新 UI

onResume 生命周期回调前,ViewRootImpl 还没创建,requestLayout 未调用,那么 checkThread() 也就调用不到。

使用子线程更新 UI 有实际应用场景吗?

拥有窗口(Window)展示的 View,其 UI 线程可以独立于 App 主线程,如 Dialog、DialogFragment、PopupWindow、Toast、SnackBar 及自定义通过 WindowManager 添加的等

这两个 View 是根红苗正用来子线程更新 View 的,SurfaceView 使用自带 Surface 去做画面渲染,TextureView 同样可以通过 TextureView#lockCanvas() 使用临时的 Surface,所以都不会触发 View#requestLayout()。

将弹窗(dialog 的实例化、inflate)与 App 其他业务相对独立的场景移到子线程运行(条件是创建 ViewRootImpl 的线程和更新 UI 的线程是一致的)

不适用多 UI 线程场景:

Activity 的 onCreate 方法为什么无法获取 View 的宽和高?

这个问题和子线程不能更新 UI 的问题很像,也是方法执行时机的一个问题。View 的 measure、layout、draw 发生在 Activity.onResume() 之后,因此在 onResume() 之前都是无法获取 View 的宽、高等信息的。